package com.nvwa.coding.util.num;

import com.fasterxml.jackson.annotation.JsonValue;
import lombok.Getter;

import java.util.Optional;

/**
 * 一个表示范围的抽象类，封装了一些常用方法
 *
 * @author wangjing
 * @since 1.0
 */
@Getter
public abstract class AbstractRange<T extends Comparable<T>> {
    /**
     * 范围下限
     */
    private final Bound<T> lowerBound;

    /**
     * 范围上限
     */
    private final Bound<T> upperBound;

    AbstractRange(Bound<T> lowerBound, Bound<T> upperBound) {
        if (null == lowerBound) {
            throw new IllegalArgumentException("下限不能为 null");
        }
        if (null == upperBound) {
            throw new IllegalArgumentException("上限不能为 null");
        }
        this.lowerBound = lowerBound;
        this.upperBound = upperBound;
    }

    @JsonValue
    @Override
    public String toString() {
        return String.format("%s,%s", lowerBound.toPrefixString(), upperBound.toSuffixString());
    }

    /**
     * 返回给定值是否在区间范围内
     *
     * @param value 要判断的值，不能为null
     * @return 给定值是否在区间范围内
     */
    public boolean contains(T value) {
        if (value == null) {
            throw new IllegalArgumentException("参考值不能为 null");
        }

        boolean greaterThanLowerBound = lowerBound.getValue()
                .map(it -> lowerBound.isInclusive() ? it.compareTo(value) <= 0 : it.compareTo(value) < 0)
                .orElse(true);

        boolean lessThanUpperBound = upperBound.getValue()
                .map(it -> upperBound.isInclusive() ? it.compareTo(value) >= 0 : it.compareTo(value) > 0)
                .orElse(true);

        return greaterThanLowerBound && lessThanUpperBound;
    }

    /**
     * 返回给定值与范围的关系。
     * <ul>
     *     <li>-1：表示值小于范围</li>
     *     <li>0：表示值在范围内</li>
     *     <li>1：表示值大于范围</li>
     * </ul>
     *
     * @param value 要判断的值，不能为null
     * @return 给定值与范围的关系
     */
    public int inRange(T value) {
        if (value == null) {
            throw new IllegalArgumentException("参考值不能为 null");
        }

        Boolean greaterThanLowerBound = lowerBound.getValue()
                .map(it -> lowerBound.isInclusive() ? it.compareTo(value) <= 0 : it.compareTo(value) < 0)
                .orElse(true);
        if (!greaterThanLowerBound) {
            return -1;
        }

        Boolean lessThanUpperBound = upperBound.getValue()
                .map(it -> upperBound.isInclusive() ? it.compareTo(value) >= 0 : it.compareTo(value) > 0)
                .orElse(true);
        if (!lessThanUpperBound) {
            return 1;
        }

        return 0;
    }


    @Override
    public boolean equals(Object o) {
        if (this == o) {
            return true;
        }

        if (!(o instanceof AbstractRange)) {
            return false;
        }

        AbstractRange<?> range = (AbstractRange<?>) o;

        if (!nullSafeEquals(lowerBound, range.lowerBound)) {
            return false;
        }

        return nullSafeEquals(upperBound, range.upperBound);
    }

    @Override
    public int hashCode() {
        int result = nullSafeHashCode(lowerBound);
        result = 31 * result + nullSafeHashCode(upperBound);
        return result;
    }

    /**
     * 表示边界的值对象。
     */
    public static final class Bound<T extends Comparable<T>> {
        private static final Bound<?> UNBOUNDED = new Bound<>(null, false);

        /**
         * 边界值
         */
        private final T value;

        /**
         * 是否包含边界值
         */
        @Getter
        private final boolean inclusive;

        private Bound(T value, boolean inclusive) {
            this.value = value;
            this.inclusive = inclusive;
        }

        /**
         * 创建一个无边界的 {@link Bound}
         */
        @SuppressWarnings("unchecked")
        public static <T extends Comparable<T>> Bound<T> unbounded() {
            return (Bound<T>) UNBOUNDED;
        }

        /**
         * 返回此边界是否有界
         *
         * @return 此边界是否有界
         */
        public boolean isBounded() {
            return value != null;
        }

        /**
         * 创建一个包含边界值的 {@link Bound}。
         * 如果值为 {@code null}，则将创建一个无边界的 {@link Bound}
         *
         * @param value 边界值，该值可为 {@code null}
         * @return 一个包含边界值的 {@link Bound}
         */
        public static <T extends Comparable<T>> Bound<T> inclusive(T value) {
            if (value == null) {
                return Bound.unbounded();
            }
            return new Bound<>(value, true);
        }

        /**
         * 创建一个不包含边界值的 {@link Bound}。
         * 如果值为 {@code null}，则将创建一个无边界的 {@link Bound}
         *
         * @param value 边界值，该值可为 {@code null}
         * @return 一个不包含边界值的 {@link Bound}
         */
        public static <T extends Comparable<T>> Bound<T> exclusive(T value) {
            if (value == null) {
                return Bound.unbounded();
            }
            return new Bound<>(value, false);
        }

        public Optional<T> getValue() {
            return Optional.ofNullable(this.value);
        }

        /**
         * 区间下边界并集计算，计算规则如下：
         * <ul>
         *     <li>无界最大，均为无界则返回this</li>
         *     <li>均有界的情况下，值小的返回</li>
         *     <li>均有界的情况下且值相等的情况下，谁包含边界值返回谁，均包含则返回this</li>
         * </ul>
         *
         * @param o 要合并的另一个区间的下边界
         * @return 两个下边界包含区间更大的一个
         */
        public Bound<T> lowerUnion(Bound<T> o) {
            if (this.isBounded()) {
                if (o.isBounded()) {
                    int i = this.value.compareTo(o.value);
                    if (i == 0) {
                        // 都有界且值相等，谁是包含边界返回谁
                        return this.inclusive ? this : o;
                    }
                    // 都有界，谁的值小返回谁
                    return i < 0 ? this : o;
                }
                // o 无界
                return o;
            }
            // this 无界
            return this;
        }

        /**
         * 区间上边界并集计算，计算规则如下：
         * <ul>
         *     <li>无界最大，均为无界则返回this</li>
         *     <li>均有界的情况下，值大的返回</li>
         *     <li>均有界的情况下且值相等的情况下，谁包含边界值返回谁，均包含则返回this</li>
         * </ul>
         *
         * @param o 要合并的另一个区间的上边界
         * @return 两个上边界包含区间更大的一个
         */
        public Bound<T> upperUnion(Bound<T> o) {
            if (this.isBounded()) {
                if (o.isBounded()) {
                    int i = this.value.compareTo(o.value);
                    if (i == 0) {
                        // 都有界且值相等，谁是包含边界返回谁
                        return this.inclusive ? this : o;
                    }
                    // 都有界，谁的值大返回谁
                    return i > 0 ? this : o;
                }
                // o 无界
                return o;
            }
            // this 无界
            return this;
        }

        String toPrefixString() {
            return getValue()
                    .map(Object::toString)
                    .map(it -> isInclusive() ? "[".concat(it) : "(".concat(it))
                    .orElse("(-∞");
        }

        String toSuffixString() {
            return getValue()
                    .map(Object::toString)
                    .map(it -> isInclusive() ? it.concat("]") : it.concat(")"))
                    .orElse("∞)");
        }

        @Override
        public String toString() {
            if (value == null) {
                return "(-∞,∞)";
            }
            return value.toString();
        }

        @Override
        public boolean equals(Object o) {

            if (this == o) {
                return true;
            }

            if (!(o instanceof Bound)) {
                return false;
            }

            Bound<?> bound = (Bound<?>) o;

            if (inclusive != bound.inclusive) {
                return false;
            }

            return nullSafeEquals(value, bound.value);
        }

        @Override
        public int hashCode() {
            int result = nullSafeHashCode(value);
            result = 31 * result + (inclusive ? 1 : 0);
            return result;
        }
    }

    static boolean nullSafeEquals(Object o1, Object o2) {
        // 比较的两个对象引用地址相同，说明对象为同一个，返回true
        if (o1 == o2) {
            return true;
        }
        // 如果两个对象存在为null的，则返回false
        if (o1 == null || o2 == null) {
            return false;
        }
        // 由于上面的if校验，说明o1不为null，这里调用equals方法比较两个对象是否相等
        if (o1.equals(o2)) {
            return true;
        }
        // 这里是比较两个数组对象，调用了arrayEquals方法比较
        if (o1.getClass().isArray() && o2.getClass().isArray()) {
            return arrayEquals((Object[]) o1, (Object[]) o2);
        }
        return false;
    }


    static boolean arrayEquals(Object[] a, Object[] a2) {
        if (a == a2) return true;
        if (a == null || a2 == null) {
            return false;
        }
        int length = a.length;
        if (a2.length != length) {
            return false;
        }
        for (int i = 0; i < length; i++) {
            if (a[i] != a2[i]) {
                return false;
            }
        }
        return true;
    }

    static int nullSafeHashCode(Object obj) {
        return obj == null ? 0 : obj.hashCode();
    }
}
